#include "concurrentleppbisection.h"
#include <QtCore/QMutexLocker>
#include <QtCore/QFuture>
#include <QtCore/QList>
#include <QtConcurrentRun>
#include <iostream>

ConcurrentLeppBisection::ConcurrentLeppBisection(Mesh *m,SelectionCriterion *c)
: LeppBisection(m,c) {}

ConcurrentLeppBisection::~ConcurrentLeppBisection() {
}

void ConcurrentLeppBisection::processMesh(){
	//QThreadPool::globalInstance()->setMaxThreadCount(4);
	QList< QFuture<void> > futures;
	QMap<ThreadSafeTriangle *,bool>::iterator work_set_it;
	for(work_set_it=work_set.begin();work_set_it!=work_set.end();work_set_it++){
		futures.push_back(QtConcurrent::run(this, &ConcurrentLeppBisection::processItem, work_set_it));
	}
	QList< QFuture<void> >::iterator it;
	for(it=futures.begin();it!=futures.end();it++){
		it->waitForFinished();
	}
	/*int threads = QThreadPool::globalInstance()->maxThreadCount();
	QFuture<void> futures[threads];
	for(int i=0;i<threads;i++){
		int size = (int)(work_set.size()/threads);
		std::cout << size << std::endl;
		if(i==0)
			int size = work_set.size()-size*(threads-1);
		futures[i] = QtConcurrent::run(this, &ConcurrentLeppBisection::processMeshPart, i*size, size);
	}
	std::cout << std::endl;
	for(int i=0;i<threads;i++){
		futures[i].waitForFinished();
	}*/
}

void ConcurrentLeppBisection::processMeshPart(int offset,int size){
	QMap<ThreadSafeTriangle *,bool>::iterator i = work_set.begin();
	i+=offset;
	QMap<ThreadSafeTriangle *,bool>::iterator end = work_set.begin();
	end+=(offset+size);
	for(;i!=end;i++){
		mutex.lock();
		if(i.value()){
			ThreadSafeTriangle *t = i.key();
			mutex.unlock();
			if(t->getMutex()->tryLock()){
				processTriangle(t);
			}
		} else {
			mutex.unlock();
		}
	}
}

void ConcurrentLeppBisection::processItem(QMap<ThreadSafeTriangle *,bool>::iterator &i){
	//std::cout << QThread::currentThread() << " empezando" << std::endl;
	mutex.lock();
	if(i.value()){
		ThreadSafeTriangle *t = i.key();
		mutex.unlock();
		if(t->getMutex()->tryLock())
		{
			processTriangle(t);
		}
		else {
			//std::cout << QThread::currentThread() << " lock failed" << std::endl;
		}
		// si no pude obtener el lock entonces sigo inmediatamente con el siguiente,
		// significa que esta siendo refinado por un thread
	} else {
		mutex.unlock();
		//std::cout << QThread::currentThread() << " previamente refinado" << std::endl;
	}
}

void ConcurrentLeppBisection::processTriangle(ThreadSafeTriangle *t){
	//std::cout << QThread::currentThread() << " procesando" << std::endl;
	assert(t!=0);
	bool working = true;
	while(working){
		//std::cout << QThread::currentThread() << " obteniedo vecino" << std::endl;
		ThreadSafeTriangle *u = t->getNeighbor(t->longestEdgeIndex());
		if(u==0){
			//std::cout << QThread::currentThread() << " cortando (borde)" << std::endl;
			refineTriangle(t,0);
			working = false;
		} else {
			Edge e_u = u->getEdge(u->longestEdgeIndex());
			Edge e_t = t->getEdge(t->longestEdgeIndex());
			if(e_u.sameVertexsId(&e_t))
			{
				//std::cout << QThread::currentThread() << " cortando" << std::endl;
				refineTriangle(t,u);
				working = false;
			}
			else
			{
				//std::cout << QThread::currentThread() << " avanzando" << std::endl;
				processTriangle(u);
			}
		}
	}
}

void ConcurrentLeppBisection::refineTriangle(ThreadSafeTriangle *t, ThreadSafeTriangle *u){
	Triangle* originals[2];
	originals[T] = t;
	originals[U] = u;

	//organizo los vertices de los triangulos
	Vertex* vertexes[2][3];
	int aux_t_longest_edge = t->longestEdgeIndex();
	vertexes[T][SHARED] = t->getVertex(aux_t_longest_edge);
	vertexes[T][FIRST_NON_SHARED] = t->getVertex((aux_t_longest_edge+1)%3);
	vertexes[T][SECOND_NON_SHARED] = t->getVertex((aux_t_longest_edge+2)%3);
	if(u!=0){
		int aux_u_longest_edge = u->longestEdgeIndex();
		vertexes[U][SHARED] = u->getVertex(aux_u_longest_edge);
		vertexes[U][FIRST_NON_SHARED] = vertexes[T][FIRST_NON_SHARED];
		vertexes[U][SECOND_NON_SHARED] = vertexes[T][SECOND_NON_SHARED];
	}

	//pruebas
	for(int k=0;k<(u==0?1:2);k++){
		for(int i=0;i<3;i++){
			for(int j=0;j<i;j++){
				assert(vertexes[k][i]!=vertexes[k][j]);
				assert(vertexes[k][i]->getId()!=vertexes[k][j]->getId());
			}
		}
	}
	assert(vertexes[T][FIRST_NON_SHARED]!=vertexes[T][SECOND_NON_SHARED]);
	assert(vertexes[T][FIRST_NON_SHARED]->getId()!=vertexes[T][SECOND_NON_SHARED]->getId());
	if(u!=0){
		assert(vertexes[T][SHARED]!=vertexes[U][SHARED]);
		assert(vertexes[T][SHARED]->getId()!=vertexes[U][SHARED]->getId());
	}

	//creo nuevo vertice
	Edge e_t = t->getEdge(t->longestEdgeIndex());
	mesh->getMutex()->lock();
	Vertex *new_vertex = new Vertex(mesh->getVertexsHighestId()+1,e_t.midPoint());
	mesh->setVertex(mesh->getVertexsHighestId()+1,new_vertex);
	mesh->getMutex()->unlock();

	//creo los nuevos triangulos
	Triangle* new_triangles[2][2];
	for(int i=0;i<2;i++){
		for(int j=0;j<2;j++){
			if(i==U&&u==0){
				new_triangles[i][j] = 0;
			} else {
				QMutexLocker locker(mesh->getMutex());
				int id = mesh->getTrianglesHighestId()+1;
				//construyo
				new_triangles[i][j] = new Triangle(
						id,
						vertexes[i][SHARED],
						new_vertex,
						vertexes[i][j]);
				//lo mando al mesh
				mesh->setTriangle(id,new_triangles[i][j]);
			}
		}
	}

	//arreglo los vecinos
	for(int i=0;i<2;i++){
		for(int j=0;j<2;j++){
			if(!(i==U&&u==0)){
				//defino sus vecinos
				Triangle *new_neighbor = originals[i]->getNeighborOposedToVertex(vertexes[i][(j+1)%2]);
				new_triangles[i][j]->setNeighbors(
						new_triangles[(i+1)%2][j],
						new_neighbor,
						new_triangles[i][(j+1)%2]);
				//arreglo el vecino del nuevo triangulo
				if(new_neighbor!=0){
					new_neighbor->replaceNeighbor(originals[i],new_triangles[i][j]);
				}
			}
		}
	}

	//pruebas

	//destruyo los viejos
	QMutexLocker locker(mesh->getMutex());
	removeFromPending(t);
	mesh->destroyTriangle(t->getId());
	if(u!=0){
		mesh->destroyTriangle(u->getId());
		removeFromPending(u);
	}
}

int ConcurrentLeppBisection::getDone(){
	QMutexLocker locker(&mutex);
	return done;
}

void ConcurrentLeppBisection::removeFromPending(ThreadSafeTriangle *t){
	QMutexLocker locker(&mutex);
	if(work_set.contains(t)){
		work_set[t] = false;
		done++;
	}
}
